<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>操作原型的方法</title>
</head>
<body>
<script>
    /* 引言：JS的对象继承是通过原型链来继承的，于是ES6 为方便操作原型，提供了一些方法 */
    /**
     * 属性：__proto__【是方法还是属性，我现在也不确定】
     * 功能：用来 读取或设置 当前对象的prototype对象【基本所有浏览器IE11】
     * 注意：本质是内部属性，不是正式对外API，浏览器广泛支持，才被加入了 ES6 很多角度来说，最好不要使用
     *      可以使用：1. Object.setPrototypeOf()（写操作）
     *               2. Object.getPrototypeOf()（读操作）
     *               3. Object.create()（生成操作）==> 代替
     * */
    // es5 的写法
    const obj = {
        method: function() {
          //...
        }
    };
    obj.__proto__ = someOtherObj;

    // es6 的写法
    var obj = Object.create(someOtherObj);
    obj.method = function() {
      //...
    };
    // 关于__proto__ 内部实现：__proto__调用的是Object.prototype.__proto__【基本上看不太懂】
    Object.defineProperty(Object.prototype, '__proto__', { //
      get() {
        let _thisObj = Object(this);
        return Object.getPrototypeOf(_thisObj);
      },
      set(proto) {
        if (this === undefined || this === null) {
          throw new TypeError();
        }
        if (!isObject(this)) {
          return undefined;
        }
        if (!isObject(proto)) {
          return undefined;
        }
        let status = Reflect.setPrototypeOf(this, proto);
        if (!status) {
          throw new TypeError();
        }
      },
    });

    function isObject(value) {
      return Object(value) === value;
    }// 对象本身部署了__proto__属性，该属性的值就是对象的原型
    Object.getPrototypeOf({ __proto__: null }) // null

    /**
     * 方法：Object.setPrototypeOf()【ES6 正式推荐设置原型对象的方法】
     * 功能：设置对象的prototype对象
     * 返回：参数本身
     * 参数：1. 需要设置的对象
     *      2. 目标原型
     * */
    // 格式
    Object.setPrototypeOf(object, prototype)
    // 用法
    const o = Object.setPrototypeOf({}, null);
    // 效果同下：
    function setPrototypeOf(obj, proto) {
      obj.__proto__ = proto;
      return obj;
    }
    // 例子时间到：将 proto 对象设为 obj 对象的原型，所以从 obj 对象可以读取 proto 对象的属性。
    let proto = {};
    let obj = { x: 10 };
    Object.setPrototypeOf(obj, proto);

    proto.y = 20;
    proto.z = 40;

    obj.x // 10
    obj.y // 20
    obj.z // 40

    // 注意事项：
    // 1、第一个参数不是对象，会自动转为对象【由于返回的还是第一个参数，所以这个操作不会产生任何效果】
    Object.setPrototypeOf(1, {}) === 1 // true
    Object.setPrototypeOf('foo', {}) === 'foo' // true
    Object.setPrototypeOf(true, {}) === true // true
    /* undefined和null无法转为对象，所以如果第一个参数是undefined或null，就会报错 */
    Object.setPrototypeOf(undefined, {})
    // TypeError: Object.setPrototypeOf called on null or undefined

    Object.setPrototypeOf(null, {})
    // TypeError: Object.setPrototypeOf called on null or undefined

    /**
     * 方法：Object.getPrototypeOf()
     * 功能：读取一个对象的原型对象
     * 参数：obj【被读取对象】
     * Object.getPrototypeOf(obj);
     * */
    // 例子时间到：
    function Rectangle() {
      // ...
    }
    const rec = new Rectangle();
    Object.getPrototypeOf(rec) === Rectangle.prototype// true
    Object.setPrototypeOf(rec, Object.prototype); // 改为object的原型
    Object.getPrototypeOf(rec) === Rectangle.prototype// false

    // 同样注意：参数不是对象，我转；undefined 和 null 报错

    // 等同于 Object.getPrototypeOf(Number(1))
    Object.getPrototypeOf(1)
    // Number {[[PrimitiveValue]]: 0}

    // 等同于 Object.getPrototypeOf(String('foo'))
    Object.getPrototypeOf('foo');
    // String {length: 0, [[PrimitiveValue]]: ""}

    // 等同于 Object.getPrototypeOf(Boolean(true))
    Object.getPrototypeOf(true);
    // Boolean {[[PrimitiveValue]]: false}

    Object.getPrototypeOf(1) === Number.prototype // true
    Object.getPrototypeOf('foo') === String.prototype // true
    Object.getPrototypeOf(true) === Boolean.prototype // true

    Object.getPrototypeOf(null)
    // TypeError: Cannot convert undefined or null to object
    Object.getPrototypeOf(undefined)
    // TypeError: Cannot convert undefined or null to object
</script>
</body>
</html>